什么是终端驱动程序

用户程序的例子有vi、emacs、pine、more、lynx、hangman、robots 等, 这些程序设置终端驱动程序的击键和输出处理方式.驱动程序有很多设置,但是用户 程序常用的到有:

  1. 立即响应击键时间
  2. 有限的输入集
  3. 输入的超时
  4. 屏蔽Ctrl-C

终端驱动程序的模式

规范模式

驱动程序输入的字符保存在缓冲区, 并且在接收到回车键的时才将缓冲区的字符 发送到程序. 缓冲区数据使驱动程序可以实现最基本的编辑功能.

非规范模式

当缓冲区和编辑功能被关闭时, 连接被称为处于非规范模式. 终端处理器仍旧进 行特定的字符处理, 但是不能编辑, 相应的输入(删除)被视作常规的数据输入.

如果希望用户能够编辑输入, 需要你的程序中实现编辑功能

raw 模式

当所有处理被关闭后, 驱动程序将输入直接传递给程序, 这种情况下, 驱动程序 被称为处于 raw 模式.

play_again.c

#include <stdio.h>
#include <termios.h>

#define QUESTION "Do you want another transaction"

int get_response(char *);

int main()
{
    int response;
    response = get_response(QUESTION);
    return response;
}

int get_response(char *question)
{
    printf("%s (y/n)?", question);
    while(1){
        switch(getchar()){
            case 'y':
            case 'Y': return 0;
            case 'n':
            case 'N':
            case EOF: return 1;
        }
    }
}

这个程序必须在用户按回车键的时候才对数据进行处理, 因此, play_again0 会 把下面输入作为一个否定的回答

$ ./play_again0
Do you want another transaction (y/n)? sure thing!

第一个改进是关闭规范输入, 使得程序能够在用户敲键的同时得到输入的字符

paly_agin1.c

#include <stdio.h>
#include <termios.h>

#define QUESTION "Do you want another transaction"

int get_response(char *);
void tty_mode(int);
void set_crmode();

int main()
{
    int response;
    tty_mode(0);        /* save tty mode */
    set_crmode();       /* set chr-by-chr mode */
    response = get_response(QUESTION);
    tty_mode(1);        /* restore tty mode */
    return response;
}

int get_response(char *question)
{
    int input;
    printf("%s (y/n)?", question);
    while(1){
        switch((input = getchar())){
            case 'y':
            case 'Y': return 0;
            case 'n':
            case 'N':
            case EOF: return 1;
            default:
                      printf("\ncannot understan %c, ", input);
                      printf("Please type y or n\n");
        }
    }
}

void set_crmode()
{
    struct termios ttystate;
    tcgetattr(0, &ttystate);
    ttystate.c_lflag     &= ~ICANON;  /* no buffering */
    ttystate.c_cc[VMIN]  = 1;   /* get 1 char at a time */
    tcsetattr(0, TCSANOW, &ttystate);
}

void tty_mode(int how)
{
    static struct termios original_mode;
    if (how == 0)
        tcgetattr(0, &original_mode);
    else
        tcsetattr(0, TCSANOW, &original_mode);
}

play_again2.c ---- 忽略非法键

#include <stdio.h>
#include <termios.h>

#define QUESTION "Do you want another transaction"

int get_response(char *);
void tty_mode(int);
void set_crmode();
void set_cr_noecho_mode();

int main()
{
    int response;
    tty_mode(0);        /* save tty mode */
    set_cr_noecho_mode();  /* set -icanon, -echo */
    response = get_response(QUESTION);
    tty_mode(1);        /* restore tty mode */
    return response;
}

int get_response(char *question)
{
    int input;
    printf("%s (y/n)?", question);
    while(1){
        switch((input = getchar())){
            case 'y':
            case 'Y': return 0;
            case 'n':
            case 'N':
            case EOF: return 1;
            default:
                      printf("\ncannot understan %c, ", input);
                      printf("Please type y or n\n");
        }
    }
}

void set_cr_noecho_mode()
{
    struct termios ttystate;
    tcgetattr(0, &ttystate);
    ttystate.c_lflag     &= ~ICANON;  /* no buffering */
    ttystate.c_lflag      &= ~ECHO;    /* no echo either */
    ttystate.c_cc[VMIN]  = 1;   /* get 1 char at a time */
    tcsetattr(0, TCSANOW, &ttystate);
}

void tty_mode(int how)
{
    static struct termios original_mode;
    if (how == 0)
        tcgetattr(0, &original_mode);
    else
        tcsetattr(0, TCSANOW, &original_mode);
}

非阻塞的输入: play_again3.c

可以使用 fcntl 为文件描述符开始 O_NDELAY 表示, 关闭文件描述符的 阻塞状态

非阻塞操作的内部实现非常简单. 每个文件都有一块保存未读取数据的地 方, 如果文件描述符置了 O_NDELAY 位, 并且那块空间是空的, read 调用返回0.

#include <stdio.h>
#include <termios.h>
#include <fcntl.h>
#include <string.h>

#define ASK "Do you want another transaction"
#define TRIES 3
#define SLEEPTIME 2
#define BEEP putchar('\a')

int get_response(char *, int);
void tty_mode(int);
void set_crmode();
void set_cr_noecho_mode();
void set_nodelay_mode();
char get_ok_char();

int main()
{
    int response;
    tty_mode(0);        /* save tty mode */
    set_cr_noecho_mode();  /* set -icanon, -echo */
    set_nodelay_mode();
    response = get_response(ASK, TRIES);
    tty_mode(1);        /* restore tty mode */
    return response;
}

int get_response(char *question, int maxtries)
{
    int input;
    printf("%s (y/n)?", question);
    while(1){
        sleep(SLEEPTIME);
        input = tolower(get_ok_char());
        if (input == 'y')
            return 0;

        if (input == 'n')
            return 1;

        if (maxtries-- == 0)
            return 2;
        BEEP;
    }
}

char get_ok_char()
{
    int c;
    while ((c = getchar()) != EOF && strchr("yYnN", c) == NULL)
        ;
    return c;
}

void set_cr_noecho_mode()
{
    struct termios ttystate;
    tcgetattr(0, &ttystate);
    ttystate.c_lflag     &= ~ICANON;  /* no buffering */
    ttystate.c_lflag      &= ~ECHO;    /* no echo either */
    ttystate.c_cc[VMIN]  = 1;   /* get 1 char at a time */
    tcsetattr(0, TCSANOW, &ttystate);
}

void set_nodelay_mode()
{
    int termflags;
    termflags = fcntl(0, F_GETFL);
    termflags |= O_NDELAY;
    fcntl(0, F_SETFL, termflags);
}

void tty_mode(int how)
{
    static struct termios original_mode;
    static int original_flags;
    if (how == 0){
        tcgetattr(0, &original_mode);
        original_flags = fcntl(0, F_GETFL);
    }
    else{
        tcsetattr(0, TCSANOW, &original_mode);
        fcntl(0, F_SETFL, original_flags);
    }
}